package main

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	mqtt "github.com/eclipse/paho.mqtt.golang"
	"io"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"
)

type Server struct {
	cfg        *Config
	vtoClients map[string]*DahuaVTOClient
	bafaClient *BafaClient

	lastCallClient *DahuaVTOClient
	lastCallTime   time.Time
}

func NewServer(cfg *Config) *Server {
	ser := &Server{
		cfg:        cfg,
		vtoClients: make(map[string]*DahuaVTOClient),
	}
	if cfg.GpioOut != "call_notify" && cfg.GpioOut != "" {
		err := initGPIO(cfg.GpioOut, true)
		if err != nil {
			logger.Printf("init gpio %s error %v\n", cfg.GpioOut, err)
		}
	}
	if cfg.BafaKey != "" {
		bafaClient, err := NewBafaClient(cfg.BafaKey)
		if err == nil {
			ser.bafaClient = bafaClient
			tpName := "callnotify006"
			_, err = bafaClient.GetTopic(tpName)
			if err != nil {
				if strings.Contains(err.Error(), "主题不存在") {
					err = bafaClient.CreateTopic("门禁呼叫", tpName)
					if err != nil {
						logger.Printf("初始化巴法云呼叫按钮: %v", err)
					}
				} else {
					logger.Printf("获取巴法云呼叫按钮: %v", err)
				}
			}
			_ = bafaClient.Sub(tpName, func(client mqtt.Client, msg mqtt.Message) {})
		} else {
			logger.Printf("init bafa client error %v\n", err)
		}
	}
	for _, vto := range cfg.VTOs {
		ser.NewVtoClient(vto)
	}
	return ser
}

func (s *Server) Start(addr string) error {
	s.registerRoutes()
	logger.Printf("HTTP server listening on %s\n", addr)
	return http.ListenAndServe(addr, nil)
}

func (s *Server) Close() {
	for _, client := range s.vtoClients {
		client.Close()
	}
	for _, vto := range s.cfg.VTOs {
		if vto.GPIO != 0 {
			cleanupGPIO(vto.GPIO)
		}
	}
}

// Add this new method to the Server struct
func (s *Server) authMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		username, password, ok := r.BasicAuth()
		if !ok || username != s.cfg.Username || password != s.cfg.Password {
			w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
			s.error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}
		next(w, r)
	}
}

func (s *Server) registerRoutes() {
	uiTem, _ := os.ReadFile("/etc/config/dahuavtoui")
	//uiTem, _ := os.ReadFile("ui.html")
	http.HandleFunc("/", s.authMiddleware(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/html")
		if uiTem != nil {
			w.Write(uiTem)
		}
	}))
	http.HandleFunc("/api/vtos", s.authMiddleware(s.handleGetVTOS))
	http.HandleFunc("/api/open", s.authMiddleware(s.handleOpenDoor))
	http.HandleFunc("/api/test", s.authMiddleware(s.handleTest))
	http.HandleFunc("/api/config", s.authMiddleware(s.handleConfig))
	http.HandleFunc("/api/log", s.authMiddleware(s.handleLog))
}

func (s *Server) handleGetVTOS(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(s.vtoClients)
}

func (s *Server) handleCallNotify(client *DahuaVTOClient, userId string) {
	if s.cfg.GpioOut != "call_notify" {
		err := callNotify(fmt.Sprintf("gpio%s", s.cfg.GpioOut))
		if err != nil {
			logger.Println(err.Error())
		}
	}
	if s.bafaClient != nil {
		s.bafaClient.client.Publish("callnotify006", 0, false, "on")
		go func() {
			time.Sleep(time.Second * 28)
			s.bafaClient.client.Publish("callnotify006", 0, false, "off")
		}()
	}
	s.lastCallClient = client
	s.lastCallTime = time.Now()

}

func (s *Server) handleOpenDoor(w http.ResponseWriter, r *http.Request) {
	id := r.URL.Query().Get("id")
	if id == "" {
		s.error(w, "Bad request", http.StatusBadRequest)
		return
	}
	client, ok := s.vtoClients[id]
	if !ok {
		s.error(w, "VTO not found", http.StatusNotFound)
		return
	}
	s.openDoor(client)
	s.writeJson(w, map[string]any{
		"result": "success",
	}, http.StatusOK)
}

func (s *Server) handleTest(w http.ResponseWriter, request *http.Request) {
	if s.bafaClient != nil {
		s.bafaClient.client.Publish("callnotify004", 0, false, "on")
		go func() {
			time.Sleep(30 * time.Second)
			s.bafaClient.client.Publish("callnotify004", 0, false, "off")
		}()
	}
	if s.cfg.GpioOut != "call_notify" {
		err := callNotify(fmt.Sprintf("gpio%s", s.cfg.GpioOut))
		if err != nil {
			s.error(w, "callNotify test error", http.StatusNotFound)
			return
		}
	}
	s.writeJson(w, map[string]any{
		"result": "success",
	}, http.StatusOK)
}

func (s *Server) handleLog(w http.ResponseWriter, r *http.Request) {
	file, err := os.OpenFile(s.cfg.LogOut, os.O_RDONLY, os.ModePerm)
	if err != nil {
		s.error(w, "Open log failed: "+err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	_, _ = io.Copy(w, file)
}

func (s *Server) error(w http.ResponseWriter, data string, code int) {
	s.writeJson(w, map[string]any{
		"result":  "error",
		"message": data,
	}, code)
}

func (s *Server) writeJson(w http.ResponseWriter, data any, code int) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	err := json.NewEncoder(w).Encode(data)
	if err != nil {
		logger.Printf("write json error: %v", err)
		return
	}
}

func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	switch r.Method {
	case http.MethodGet:
		marshal, err := json.Marshal(s.cfg)
		if err != nil {
			return
		}
		_, _ = w.Write(marshal)
	case http.MethodPost, http.MethodPut:
		var cfg = &Config{}
		err := json.NewDecoder(r.Body).Decode(cfg)
		if err != nil {
			s.error(w, "Failed to Decode config: "+err.Error(), http.StatusInternalServerError)
			return
		}
		if cfg.Password == "" {
			cfg.Password = s.cfg.Password
		}
		cfg.LogOut = s.cfg.LogOut
		err = SaveConfigToUCI(cfg, *configFile)
		if err != nil {
			s.error(w, "Failed to write config: "+err.Error(), http.StatusInternalServerError)
			return
		}
		s.writeJson(w, map[string]any{
			"result":  "success",
			"message": "Config updated successfully",
		}, http.StatusOK)
		go func() {
			time.Sleep(time.Second)
			cmd := exec.Command("/etc/init.d/dahuavto", "restart")
			output, err := cmd.CombinedOutput()
			if err != nil {
				logger.Printf("Config updated but restart failed: " + err.Error() + "\n" + string(output))
				return
			}
		}()
	default:
		s.error(w, "Method not allowed", http.StatusMethodNotAllowed)
	}
}

func (s *Server) NewVtoClient(vto *DahuaVTO) {
	users := []string{s.cfg.UserID}
	if vto.UserID != "" {
		users = append(users, vto.UserID)
	}
	client := NewDahuaVTOClient(vto.Name, vto.Password, users, s.handleCallNotify, false)
	port := 80
	if vto.Port != 0 {
		port = vto.Port
	}
	s.vtoClients[vto.Id] = client
	go func() {
		for {
			if err := client.Connect(vto.IP, port, time.Second*3); err != nil {
				logger.Printf("VTO[%s] 连接失败: %v", vto.IP, err)
				vto.Error = err.Error()
			} else {
				logger.Printf("VTO[%s] 连接成功", vto.IP)
				vto.IsConnected = true
				break
			}
			time.Sleep(time.Second * 5)
		}
		if vto.GPIO != 0 {
			s.initGpio(vto.GPIO, client)
		}
		if vto.BtnName != "" && s.bafaClient != nil {
			tpName := getFirst5CharsOfMD5(vto.BtnName) + "006"
			_, err := s.bafaClient.GetTopic(tpName)
			if err != nil {
				if strings.Contains(err.Error(), "主题不存在") {
					err = s.bafaClient.CreateTopic(vto.BtnName, getFirst5CharsOfMD5(vto.BtnName)+"006")
					if err != nil {
						logger.Printf("VTO[%s] 创建巴发云: %v", vto.IP, err)
					}
					_, err = s.bafaClient.GetTopic(tpName)
					if err != nil {
						return
					}
				} else {
					logger.Printf("VTO[%s] 获取巴法云topic: %v", vto.IP, err)
				}
			}
			err = s.bafaClient.Sub(tpName, func(cc mqtt.Client, msg mqtt.Message) {
				msg.Ack()
				if string(msg.Payload()) == "on" {
					s.openDoor(client)
					cc.Publish(tpName, 0, false, "off")
				}
			})
		}
	}()
}

func getFirst5CharsOfMD5(input string) string {
	// 计算MD5哈希
	hasher := md5.New()
	hasher.Write([]byte(input))
	hashBytes := hasher.Sum(nil)

	// 转换为十六进制字符串
	hashString := hex.EncodeToString(hashBytes)

	// 取前5个字符
	if len(hashString) >= 5 {
		return hashString[:5]
	}
	return hashString
}

func (s *Server) openDoor(client *DahuaVTOClient) {
	if !s.lastCallTime.IsZero() {
		if time.Since(s.lastCallTime) <= 30*time.Second && s.lastCallClient != nil {
			err := s.lastCallClient.OpenDoorAndCancelCall("", time.Second*3)
			if err != nil {
				logger.Printf("open door error %v\n", err)
			}
			if s.bafaClient != nil {
				s.bafaClient.client.Publish("callnotify006", 0, false, "off")
			}
			return
		}
	}
	err := client.OpenDoorAndCancelCall("", time.Second*3)
	if err != nil {
		logger.Printf("open door error %v\n", err)
	}
}

func (s *Server) initGpio(gpio int, client *DahuaVTOClient) {
	err := initGPIO(fmt.Sprint(gpio), false)
	if err != nil {
		logger.Printf("init gpio %d error %v\n", gpio, err)
	}
	go func() {
		err = watchGPIO(gpio, func(value int) {
			s.openDoor(client)
		})
		if err != nil {
			logger.Printf("watch gpio %d error %v\n", gpio, err)
		}
	}()
}
